﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using King.Engine.Core;
using System.Reflection;
using King.Engine.GameObjects;

namespace King.Engine.Content
{

    /// <summary>
    /// A LevelScheme defines how a level is built for a given Activity type
    /// </summary>
    public abstract class LevelScheme<T> where T:Activity
    {

        #region Fields
        
        private Dictionary<string, BuildLevelElement> _customElementBuilders;
        private static Dictionary<string, Type> _gameObjectTypes;

        #endregion


        #region Properties

        internal BuildLevelElement this[string elementName]
        {
            get
            {
                BuildLevelElement builder;
                bool isCustom = _customElementBuilders.TryGetValue(elementName, out builder);

                if (!isCustom)
                {
                    builder = (BuildLevelElement)((T activity, Dictionary<string, string> attributes) => 
                                                    BuildLevelGameObject(elementName, activity, attributes));
                }

                return builder;
            }
        }
        
        #endregion


        #region Constructors

        static LevelScheme()
        {
            _gameObjectTypes = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
                               from type in assembly.GetTypes()
                               where type.IsSubclassOf(typeof(GameObject))
                               where !type.IsAbstract
                               select type)
                               .ToDictionary(t => t.Name, t => t);
        }

        /// <summary>
        /// Initialize the level scheme
        /// </summary>
        /// <param name="customElementBuilders">The dictionary that maps element names to their builder</param>
        protected LevelScheme(Dictionary<string, BuildLevelElement> customElementBuilders)
        {
            _customElementBuilders = customElementBuilders;
        }

        #endregion


        #region Private Methods

        private static void BuildLevelGameObject(string elementName, T activity, Dictionary<string, string> attributes)
        {
            Type gameObjectType;
            bool typeExists = _gameObjectTypes.TryGetValue(elementName, out gameObjectType);

            if (!typeExists)
            {
                throw new ArgumentOutOfRangeException("The level element \"" + elementName + "\" does not have an associated builder or GameObject");
            }

            var attributeValues = attributes.Values.ToArray();

            GameObject gameObject;
            try
            {
                gameObject = Activator.CreateInstance(gameObjectType, attributeValues) as GameObject;
            }
            catch (Exception e)
            {
                throw new FormatException("An error occured when constructing the level element \"" + elementName +
                                          "\". Please ensure that the GameObject subtype has a constructor to match the attributes",
                                          e);
            }

            activity.AddGameObject(gameObject);
        }

        #endregion


        #region Inner Types

        /// <summary>
        /// Callback that builds part of a level for a certain element, using the given list of attributes
        /// </summary>
        /// <param name="activity">The current activity</param>
        /// <param name="attributes">The dictionary that maps attributes to their values</param>
        protected internal delegate void BuildLevelElement(T activity, Dictionary<string, string> attributes);

        #endregion

    }

    internal class DefaultLevelScheme<T> : LevelScheme<T> where T:Activity
    {
        public DefaultLevelScheme()
            : base(new Dictionary<string, BuildLevelElement>())
        {

        }
    }
}
